package com.idea.relax.boot.error;

import com.idea.relax.tool.api.R;
import com.idea.relax.tool.api.RCode;
import com.idea.relax.tool.core.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.Servlet;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;

/**
 * @className: RestExceptionHandler
 * @description: 全局异常处理类
 * @author: 沉香
 * @date: 2022/10/1
 **/
@Slf4j
@Configuration(
	proxyBeanMethods = false
)
@ConditionalOnWebApplication(
	type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class})
@RestControllerAdvice
public class RestExceptionHandler {


	@ExceptionHandler({Throwable.class})
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	public R<?> handleError(Throwable e) {
		log.error(RCode.INTERNAL_SERVER_ERROR.getMessage(), e);
		return R.fail(RCode.INTERNAL_SERVER_ERROR.getCode(), RCode.INTERNAL_SERVER_ERROR.getMessage());
	}

	@ExceptionHandler({MissingServletRequestParameterException.class})
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<?> handleError(MissingServletRequestParameterException e) {
		log.warn(RCode.PARAM_MISS.getMessage(), e.getMessage());
		return R.fail(RCode.PARAM_MISS.getCode(), RCode.PARAM_MISS.getMessage() + ":" + e.getParameterName());
	}

	@ExceptionHandler({MethodArgumentTypeMismatchException.class})
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<?> handleError(MethodArgumentTypeMismatchException e) {
		log.warn(RCode.PARAM_FORMAT_ERROR.getMessage(), e.getMessage());
		return R.fail(RCode.PARAM_FORMAT_ERROR.getCode(), RCode.PARAM_FORMAT_ERROR + ":" + e.getName());
	}

	@ExceptionHandler({MethodArgumentNotValidException.class})
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<?> handleError(MethodArgumentNotValidException e) {
		return handleBindError(RCode.PARAM_VALID_ERROR, e.getBindingResult(), e);
	}

	@ExceptionHandler({BindException.class})
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<?> handleError(BindException e) {
		return handleBindError(RCode.PARAM_BIND_ERROR, e.getBindingResult(), e);
	}

	private R<?> handleBindError(RCode code, BindingResult result, BindException e) {
		log.warn(code.getMessage(), e.getMessage());
		FieldError error = result.getFieldError();
		String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
		return R.fail(RCode.PARAM_BIND_ERROR.getCode(), RCode.PARAM_BIND_ERROR.getMessage() + ":" + message);
	}

	@ExceptionHandler({ConstraintViolationException.class})
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<?> handleError(ConstraintViolationException e) {
		log.warn(RCode.PARAM_VALID_ERROR.getMessage(), e.getMessage());
		Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
		ConstraintViolation<?> violation = violations.iterator().next();
		return R.fail(RCode.PARAM_VALID_ERROR.getCode(), violation.getMessage());
	}

	@ExceptionHandler({HttpMessageNotReadableException.class})
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R<?> handleError(HttpMessageNotReadableException e) {
		log.error(RCode.MSG_NOT_READABLE.getMessage() + " {}", e.getMessage());
		return R.fail(RCode.MSG_NOT_READABLE.getCode(), RCode.MSG_NOT_READABLE.getMessage());
	}

	@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
	@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
	public R<?> handleError(HttpRequestMethodNotSupportedException e) {
		log.error(RCode.METHOD_NOT_SUPPORTED.getMessage() + " {}", e.getMessage());
		return R.fail(RCode.METHOD_NOT_SUPPORTED.getCode(), RCode.METHOD_NOT_SUPPORTED.getMessage());
	}

	@ExceptionHandler({HttpMediaTypeNotSupportedException.class})
	@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
	public R<?> handleError(HttpMediaTypeNotSupportedException e) {
		log.error(RCode.MEDIA_TYPE_NOT_SUPPORTED.getMessage() + " {}", e.getMessage());
		return R.fail(RCode.MEDIA_TYPE_NOT_SUPPORTED.getCode(), RCode.MEDIA_TYPE_NOT_SUPPORTED.getMessage());
	}

	@ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
	@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
	public R<?> handleError(HttpMediaTypeNotAcceptableException e) {
		String message = e.getMessage() + " : " + StringUtil.join(e.getSupportedMediaTypes());
		log.error(RCode.MEDIA_TYPE_NOT_SUPPORTED.getMessage() + " {}", message);
		return R.fail(RCode.MEDIA_TYPE_NOT_SUPPORTED.getCode(), RCode.MEDIA_TYPE_NOT_SUPPORTED.getMessage());
	}

}
